﻿#region "Copyright (C) Lenny Granovsky. 2005-2013"
//This program is free software distribute under the terms of the GNU General Public License as published by
//the Free Software Foundation. Please read AssemblyInfo.cs file for more information.
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using System.Web;
using System.Web.Services;
using System.Web.Services.Protocols;
using System.Reflection;
using System.IO;
using System.Web.Routing;
using System.Collections.Specialized;

namespace Web.Enhancements.Rest
{
    [Serializable()]
    public class WebRestRouteHandler : CallingTargetFactory, IRouteHandler
    {
        public virtual IHttpHandler GetHttpHandler(RequestContext requestContext)
        {
            if (requestContext == null)
            {
                throw new ArgumentNullException("requestContext");
            }
#if ENABLE_TIMING
                System.Diagnostics.Stopwatch watch = null;
#endif
            try
            {
#if ENABLE_TIMING
                watch = new System.Diagnostics.Stopwatch();
                watch.Start();
                context.Items.Add("WebRestMainTimer", watch);
#endif
#if ENABLE_TIMING || TRACE
                System.Diagnostics.Trace.WriteLine(string.Format("WebRest route handler started at {0} for {1}.", DateTime.Now.ToString(), requestContext.HttpContext.Request.RawUrl));
#endif
                string substitutedUrl = this.GetSubstitutedVirtualPath(requestContext);
                string appVirtualPath = "", queryString = "";
                int index = substitutedUrl.IndexOf('?');
                if (index != -1)
                {
                    appVirtualPath = substitutedUrl.Substring(0, index);
                    queryString = substitutedUrl.Substring(index + 1);
                }
                else
                    appVirtualPath = substitutedUrl;

                object target = base.CreateInvocationContext(appVirtualPath, queryString, requestContext.HttpContext.Request.HttpMethod);
                requestContext.HttpContext.Items.Add("WebRestHanlerInvocationTarget", target);

                //call Filters before handler created
                foreach (Filters.IWebRestFilter filter in WebRestServiceContext.Current.Filters)
                {
#if TRACE
                    System.Diagnostics.Trace.WriteLine(string.Format("Calling {0} REST filter before the service handler created.", filter.GetType().FullName));
#endif
                    filter.BeforeHandlerCreated(requestContext.HttpContext.ApplicationInstance.Context, WebRestServiceContext.Current);
                    if (WebRestServiceContext.Current.Cancel)
                        break;//request will be cancelled upon handler start
                }

                switch (WebRestServiceContext.Current.SessionMode)
                {
                    case System.Web.SessionState.SessionStateBehavior.Required:
                        return new WebRestSessionEnabledHandler();
                    case System.Web.SessionState.SessionStateBehavior.ReadOnly:
                        return new WebRestSessionReadOnlyHandler();
                    default:
                        return new WebRestHandler();
                }
            }
            catch (System.Threading.ThreadAbortException)
            {
                throw;
            }
            catch (Exception e)
            {
#if TRACE
                System.Diagnostics.Trace.WriteLine(string.Format("WebRest handler factory thrown an exception. Error: {0}", e.ToString()));
#endif
                throw;
            }
            finally
            {
#if ENABLE_TIMING
                System.Diagnostics.Trace.WriteLine(string.Format("WebRest handler factory ended - {0} ticks elapsed.", watch.ElapsedTicks.ToString()));
#endif
            }
        }

        private string GetSubstitutedVirtualPath(RequestContext requestContext)
        {
            if (requestContext == null)
            {
                throw new ArgumentNullException("requestContext");
            }
            string rawUrl = requestContext.HttpContext.Request.RawUrl;
            if (rawUrl.StartsWith("/"))
                rawUrl = rawUrl.Remove(0, 1);
            int qIndex = rawUrl.IndexOf("?");
            if (qIndex > 0)
                rawUrl = rawUrl.Substring(0, qIndex);
            Route routeVirtualPath = new Route(rawUrl, this);
            VirtualPathData virtualPath = routeVirtualPath.GetVirtualPath(requestContext, requestContext.RouteData.Values);
            if (virtualPath == null)
                return requestContext.HttpContext.Request.RawUrl;
            //rebuild path to the original specification
            StringBuilder sbr = new StringBuilder("~/");
            string queryString = "";
            int index = virtualPath.VirtualPath.IndexOf('?');
            if (index != -1)
            {
                queryString = virtualPath.VirtualPath.Substring(index + 1);
                NameValueCollection qs = this.ParseQueryString(queryString);
                string key = "namespace"; //check if full namespace is set
                if (!string.IsNullOrWhiteSpace(qs[key]))
                {
                    sbr.AppendFormat("{0}/", qs[key].Replace(".", "/"));
                }
                else
                {
                    for (int ii = 0; ii < 50; ii++) //will handle up to 50 namespaces.
                    {
                        key = "ns" + ii.ToString();
                        if (string.IsNullOrWhiteSpace(qs[key]))
                            break;
                        else
                        {
                            sbr.AppendFormat("{0}/", qs[key]);
                            qs.Remove(key);
                        }
                    }
                }
                if (!string.IsNullOrWhiteSpace(qs["class"]))
                {
                    sbr.AppendFormat("{0}.rest?", qs["class"]);
                    qs.Remove("class");
                }
                index = queryString.IndexOf("class=", StringComparison.OrdinalIgnoreCase);
                if (index != -1)
                {
                    index = queryString.IndexOf("&", index + 6);
                    if (index != -1)
                        queryString = queryString.Substring(index + 1);
                }
                sbr.Append(queryString);
                //set parsed QS parameters for Method parameters
                requestContext.HttpContext.Items.Add("WebRestRoutedQueryStringParams", qs);
            }
            return sbr.ToString();
        }

        private NameValueCollection ParseQueryString(string queryString)
        {
            NameValueCollection qs = new NameValueCollection(StringComparer.OrdinalIgnoreCase);
            int num = (queryString != null) ? queryString.Length : 0;
            for (int i = 0; i < num; i++)
            {
                int startIndex = i;
                int num4 = -1;
                while (i < num)
                {
                    char ch = queryString[i];
                    if (ch == '=')
                    {
                        if (num4 < 0)
                            num4 = i;
                    }
                    else if (ch == '&')
                        break;
                    i++;
                }
                string str = null;
                string str2 = null;
                if (num4 >= 0)
                {
                    str = queryString.Substring(startIndex, num4 - startIndex);
                    str2 = queryString.Substring(num4 + 1, (i - num4) - 1);
                }
                else
                    str2 = queryString.Substring(startIndex, i - startIndex);
                qs.Add(str, str2);
                if ((i == (num - 1)) && (queryString[i] == '&'))
                    qs.Add(null, string.Empty);
            }

            return qs;
        }
    }
}
